#include "common.h"
#include "counter.h"
#include "Vbusytable.h"

#define TRACE 1
#define MAX_WAVE 1000000
#define MAX_CYCLE 200000
#define PRF_SIZE 92
#define ALLOCATE_WIDTH 2
#define RF_WRITE_WIDTH 3
#define READ_PORTS 5
#define COMMIT_LATENCY 5
Vbusytable *top;
VerilatedFstC *tfp;
Counter cycle("cycle");

bool percent(int change)
{
    uint32_t r = rand();
    r %= 100;
    return r < change;
}
uint32_t rand_n(uint32_t n)
{
    uint32_t r = rand();
    return r % n;
}
void dump()
{
#if TRACE
    if (Verilated::time() < MAX_WAVE)
    {
        tfp->dump(Verilated::time());
    }
#endif
}
void run()
{
    top->eval();
    dump();
    Verilated::timeInc(1);

    top->clk = 0;
    top->eval();
    dump();
    Verilated::timeInc(2);

    top->clk = 1;
    top->eval();
    dump();
    Verilated::timeInc(1);
}
#include <queue>
typedef struct
{
    int prf_idx;
    word_t cycle;
} ROB_Entry;

std::queue<ROB_Entry> rob;
std::queue<int> freelist;
void clear_all()
{
    for (size_t i = 0; i < ALLOCATE_WIDTH; i++)
    {
        top->allocate_valid[i] = 0;
    }

    for (size_t i = 0; i < RF_WRITE_WIDTH; i++)
    {
        top->rf_write_valid[i] = 0;
    }
    for (size_t i = 0; i < READ_PORTS; i++)
    {
        top->req_prf[i] = 0;
    }

    while (!rob.empty())
    {
        rob.pop();
    }
    for (int i = 32; i < PRF_SIZE; i++)
    {
        freelist.push(i);
    }
}
void reset()
{
    clear_all();
    top->rst = 1;
    run();
    run();
    run();
    top->rst = 0;
}

void make_rand_input()
{
    auto allocate_num = rand_n(ALLOCATE_WIDTH + 1);
    auto rf_write_num = rand_n(RF_WRITE_WIDTH + 1);
    for (size_t i = 0; i < READ_PORTS; i++)
    {
        top->req_prf[i] = rand_n(PRF_SIZE);
    }
    for (size_t i = 0; i < allocate_num; i++)
    {
        if (freelist.empty())
            break;
        top->allocate_valid[i] = 1;
        top->allocate_prf[i] = freelist.front();
        freelist.pop();
    }
    for (size_t i = 0; i < rf_write_num; i++)
    {
        if (rob.empty())
            break;
        auto rob_enrty = rob.front();
        top->rf_write_valid[i] = rob_enrty.cycle + COMMIT_LATENCY < cycle;
        top->rf_write_prf[i] = rob_enrty.prf_idx;
        rob.pop();
    }
    for (size_t i = 0; i < allocate_num; i++)
    {
        if (top->allocate_valid[i])
        {
            rob.push({top->allocate_prf[i], cycle});
        }
    }
}

void busytable()
{
    LOG("busytable");

    reset();
    do
    {
        run();
        cycle++;
        clear_all();
        make_rand_input();
    } while ((!Verilated::gotFinish()) && cycle < MAX_CYCLE);
}
int main()
{
    srand(0);
    Verilated::randSeed(0);
    Verilated::randReset(1);
    top = new Vbusytable();
#if TRACE
    Verilated::traceEverOn(true);
    tfp = new VerilatedFstC();
    top->trace(tfp, 0);
    tfp->open("Vbusytable.fst");
#endif
    reset();
    // Run all tests
    busytable();
    // Final model cleanup
    run();
    top->final();
#if TRACE
    tfp->close();
    delete tfp;
#endif
    delete top;
    Counter::print_counters();
    LOG("All test done");
    return 0;
}